﻿using EntityEntry = Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry;

namespace MicroCloud.Entity
{
    /// <summary>
    /// 数据上下文扩展方法
    /// </summary>
    public static class DbContextExtensions
    {
        #region "当前上下文是否是关系型数据库"
        /// <summary>
        /// 当前上下文是否是关系型数据库
        /// </summary>
        /// <param name="context">DbContext实例</param>
        /// <returns></returns>
        public static bool IsRelationalTransaction(this DbContext context)
        {
            return context.Database.GetService<IDbContextTransactionManager>() is IRelationalTransactionManager;
        }
        #endregion
        #region "检测关系型数据库是否存在"
        /// <summary>
        /// 检测关系型数据库是否存在
        /// </summary>
        /// <param name="context">DbContext实例</param>
        /// <returns></returns>
        public static bool ExistsRelationalDatabase(this DbContext context)
        {
            return context.Database.GetService<IDatabaseCreator>() is RelationalDatabaseCreator creator && creator.Exists();
        }
        #endregion

        #region "获取未提交的迁移记录并提交迁移"
        /// <summary>
        /// 获取未提交的迁移记录并提交迁移
        /// </summary>
        /// <param name="dbContext">DbContext实例</param>
        /// <param name="logger">日志记录器</param>
        public static void CheckAndMigration(this DbContext dbContext, ILogger logger = null)
        {
            string[] migrations = dbContext.Database.GetPendingMigrations().ToArray();
            if (migrations.Length > 0)
            {
                dbContext.Database.Migrate();
                logger?.LogInformation("{length} 条挂起的迁移记录 {migrations} 提交完成", migrations.Length, migrations.ExpandAndToString());
            }
        }
        #endregion

        #region "获取DbContext实例"
        /// <summary>
        /// 获取DbContext实例
        /// </summary>
        /// <param name="dbContext">数据上下文接口</param>
        /// <returns></returns>
        public static DbContext GetDbContextInstance(this IDbContext dbContext)
        {
            if (dbContext is not DbContext context)
            {
                throw new Exception(I18N.T("类型 {0} 不能转换为 DbContext", dbContext.GetType()));
            }
            return context;
        }
        #endregion

        #region "获取实体上下文实例和池租约（如果有）的唯一标识符"
        /// <summary>
        /// 获取实体上下文实例和池租约（如果有）的唯一标识符
        /// （此标识符主要用作日志记录和调试的相关ID，以便很容易识别多个事件正在使用相同或不同的上下文实例。）
        /// </summary>
        /// <param name="dbContext">数据上下文接口</param>
        /// <returns>实体上下文实例和池租约（如果有）的唯一标识符</returns>
        public static DbContextId GetContextId(this IDbContext dbContext)
        {
            var context = dbContext.GetDbContextInstance();
            return context.ContextId;
        }
        #endregion
        #region "获取实体上下文元数据（包括实体的形状、它们之间的关系以及它们如何映射到数据库）"
        /// <summary>
        /// 获取实体上下文元数据（包括实体的形状、它们之间的关系以及它们如何映射到数据库）
        /// </summary>
        /// <param name="dbContext">数据上下文接口</param>
        /// <returns>实体上下文元数据（包括实体的形状、它们之间的关系以及它们如何映射到数据库）</returns>
        public static IModel GetModel(this IDbContext dbContext)
        {
            var context = dbContext.GetDbContextInstance();
            return context.Model;
        }
        #endregion
        #region "获取实体上下文正在跟踪的实体实例的信息和操作"
        /// <summary>
        /// 获取实体上下文正在跟踪的实体实例的信息和操作
        /// </summary>
        /// <param name="dbContext">数据上下文接口</param>
        /// <returns>实体上下文正在跟踪的实体实例的信息和操作</returns>
        public static ChangeTracker GetChangeTracker(this IDbContext dbContext)
        {
            var context = dbContext.GetDbContextInstance();
            return context.ChangeTracker;
        }
        #endregion

        #region "获取指定类型实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。"
        /// <summary>
        /// 获取指定类型实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。
        /// </summary>
        /// <param name="dbContext"><see cref="IDbContext"/> 实例</param>
        /// <param name="entityType">实体类型</param>
        /// <returns>动态 <see cref="IQueryable{T}"/> 对象</returns>
        public static IQueryable<object> GetQueryable(this IDbContext dbContext, Type entityType)
        {
            var context = dbContext.GetDbContextInstance();
            return context.GetQueryable(entityType);
        }
        #endregion
        #region "获取指定类型实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。"
        /// <summary>
        /// 获取指定类型实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。
        /// </summary>
        /// <param name="dbContext"><see cref="DbContext"/> 实例</param>
        /// <param name="entityType">实体类型</param>
        /// <returns>动态 <see cref="IQueryable{T}"/> 对象</returns>
        public static IQueryable<object> GetQueryable(this DbContext dbContext, Type entityType)
        {
            //var dbQueryable = (IQueryable<object>)dbContext.GetType().GetMethod("Set").MakeGenericMethod(entityType).Invoke(dbContext, null);
            var entityTypeName = entityType?.Name;
            return dbContext.GetQueryable(entityTypeName);
        }
        #endregion
        #region "获取指定名称实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。"
        /// <summary>
        /// 获取指定名称实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。
        /// </summary>
        /// <param name="dbContext"><see cref="IDbContext"/> 实例</param>
        /// <param name="entityTypeName">实体类型名称 ( Type.Name --> ClrType.Name )</param>
        /// <returns>动态 <see cref="IQueryable{T}"/> 对象</returns>
        public static IQueryable<object> GetQueryable(this IDbContext dbContext, string entityTypeName)
        {
            var context = dbContext.GetDbContextInstance();
            return context.GetQueryable(entityTypeName);
        }
        #endregion
        #region "获取指定名称实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。"
        /// <summary>
        /// 获取指定名称实体的DbSet实例 <see cref="IQueryable{T}"/>，用于访问上下文和基础存储中给定类型的实体。
        /// </summary>
        /// <param name="dbContext"><see cref="DbContext"/> 实例</param>
        /// <param name="entityTypeName">实体类型名称 ( Type.Name --> ClrType.Name )</param>
        /// <returns>动态 <see cref="IQueryable{T}"/> 对象</returns>
        public static IQueryable<object> GetQueryable(this DbContext dbContext, string entityTypeName)
        {
            return dbContext.SetDynamic(entityTypeName);
        }
        #endregion

        #region "更新上下文中指定实体的状态"
        /// <summary>
        /// 更新上下文中指定实体的状态
        /// </summary>
        /// <typeparam name="TEntity">实体类型</typeparam>
        /// <typeparam name="TKey">主键类型</typeparam>
        /// <param name="context">DbContext实例</param>
        /// <param name="entities">要更新的实体类型</param>
        public static void Update<TEntity, TKey>(this DbContext context, params TEntity[] entities)
            where TEntity : class, IEntity<TKey>
        {
            Check.NotNull(entities, nameof(entities));

            DbSet<TEntity> set = context.Set<TEntity>();
            foreach (TEntity entity in entities)
            {
                try
                {
                    EntityEntry<TEntity> entry = context.Entry(entity);
                    if (entry.State == EntityState.Detached)
                    {
                        set.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
                catch (InvalidOperationException)
                {
                    TEntity oldEntity = set.Find(entity.Id);
                    context.Entry(oldEntity).CurrentValues.SetValues(entity);
                }
            }
        }
        #endregion
        #region "更新上下文中指定实体的状态"
        /// <summary>
        /// 更新上下文中指定实体的状态
        /// </summary>
        /// <typeparam name="TEntity">实体类型</typeparam>
        /// <typeparam name="TKey">主键类型</typeparam>
        /// <param name="context">DbContext实例</param>
        /// <param name="entities">要更新的实体类型</param>
        public static void Update<TEntity, TKey>(this DbContext context, IEnumerable<TEntity> entities)
            where TEntity : class, IEntity<TKey>
        {
            Check.NotNull(entities, nameof(entities));

            DbSet<TEntity> set = context.Set<TEntity>();
            foreach (TEntity entity in entities)
            {
                try
                {
                    EntityEntry<TEntity> entry = context.Entry(entity);
                    if (entry.State == EntityState.Detached)
                    {
                        set.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
                catch (InvalidOperationException)
                {
                    TEntity oldEntity = set.Find(entity.Id);
                    context.Entry(oldEntity).CurrentValues.SetValues(entity);
                }
            }
        }
        #endregion

        #region "清除数据上下文的更改"
        /// <summary>
        /// 清除数据上下文的更改
        /// </summary>
        /// <param name="context">DbContext实例</param>
        public static void CleanChanges(this DbContext context)
        {
            IEnumerable<EntityEntry> entries = context.ChangeTracker.Entries();
            foreach (var entry in entries)
            {
                entry.State = EntityState.Detached;
            }
        }
        #endregion

    }
}